package com.shenhao.hpfa.netty;

import com.fasterxml.jackson.databind.JavaType;
import com.shenhao.hpfa.model.Payload;
import com.shenhao.hpfa.netty.handler.ResponseHandler;
import com.shenhao.hpfa.properties.ControllerConfig;
import com.shenhao.hpfa.properties.HpfaProperties;
import com.shenhao.hpfa.properties.IdleTimeoutSetting;
import com.shenhao.hpfa.util.JsonUtil;
import com.shenhao.hpfa.util.LatchHolder;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
@Component
@RequiredArgsConstructor
public class HpfaNettyClient {

    private final HpfaProperties properties;

    // 创建一个多线程的定时任务线程池，线程数量根据实际情况调整
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(4, new ThreadFactory() {
        private final AtomicInteger index = new AtomicInteger();

        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r, "recon-thd-" + index.getAndIncrement());
            t.setDaemon(false);
            return t;
        }
    });

    private final EventLoopGroup group = new NioEventLoopGroup();

    // 控制器连接状态映射
    private final Map<String, AtomicBoolean> connectionStateMap = new ConcurrentHashMap<>();

    // 控制器通道映射
    private final Map<String, Channel> channelMap = new ConcurrentHashMap<>();

    @PostConstruct
    public void init() {
        // 初始化控制器映射
        properties.initControllerMap();
    }

    /**
     * 连接指定控制器
     *
     * @param deviceSn 控制器sn
     */
    public void connect(String deviceSn) {
        ControllerConfig config = properties.getController(deviceSn);
        if (config == null || !config.isEnabled()) {
            log.warn("控制器 {} 不存在或未启用", deviceSn);
            return;
        }

        Channel channel = channelMap.get(deviceSn);
        if (channel != null && channel.isOpen()) {
            log.info("控制器 {} 的通道未关闭，先关闭原通道", deviceSn);
            channel.close();
            channelMap.remove(deviceSn);
        }

        HpfaNettyClient hpfaNettyClient = this;
        Bootstrap bootstrap = new Bootstrap();
        IdleTimeoutSetting idleTimeout = properties.getIdleTimeout();
        boolean autoCloseConnection = idleTimeout.isAutoCloseConnection();
        long readMillis = idleTimeout.getReadTimeout().toMillis();
        long writeMillis = idleTimeout.getWriteTimeout().toMillis();
        long allMillis = idleTimeout.getAllTimeout().toMillis();

        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        ChannelPipeline pipeline = ch.pipeline();
                        if (autoCloseConnection) {
                            pipeline.addLast(new IdleStateHandler(readMillis, writeMillis, allMillis, TimeUnit.MILLISECONDS));
                        }
                        pipeline.addLast(new StringEncoder())
                                .addLast(new StringDecoder())
                                .addLast(new ResponseHandler(hpfaNettyClient, deviceSn));
                    }
                })
                .option(ChannelOption.SO_KEEPALIVE, true);
        String host = config.getHost();
        int port = config.getPort();
        ChannelFuture future = bootstrap.connect(host, port);
        log.info("开始连接控制器 {}，地址: {}:{}", deviceSn, host, port);
        future.addListener((ChannelFutureListener) f -> {
            if (f.isSuccess()) {
                log.info("控制器 {} 连接成功，地址: {}:{}", deviceSn, host, port);
                channelMap.put(deviceSn, f.channel());
                setConnectState(deviceSn, true);
            } else {
                log.error("控制器 {} 连接失败，准备重试...", deviceSn);
                if (properties.isAutoReconnect()) {
                    retryConnect(deviceSn);
                }
            }
        });
    }

    /**
     * 重试连接指定控制器
     *
     * @param deviceSn 控制器 sn
     */
    public void retryConnect(String deviceSn) {
        // 使用线程池延迟 800 毫秒后执行重连操作
        scheduler.schedule(() -> {
            try {
                connect(deviceSn);
            } catch (Exception e) {
                log.error("控制器 {} 重连失败", deviceSn, e);
            }
        }, 800, TimeUnit.MILLISECONDS);
    }

    /**
     * 发送命令并获取响应
     *
     * @param deviceSn 控制器sn
     * @param payload  请求负载
     * @param clazz    响应类型
     * @param <T>      响应类型
     * @return 响应结果
     */
    public <T> Payload<T> sendAndGetResp(String deviceSn, Payload<?> payload, Class<T> clazz) {
        Channel channel = channelMap.get(deviceSn);
        if (channel == null || !channel.isActive()) {
            connect(deviceSn);
            try {
                log.info("等待设备{}连接成功", deviceSn);
                Thread.sleep(150);
            } catch (InterruptedException ignored) {
            }
            channel = channelMap.get(deviceSn);
        }
        if (channel != null && channel.isActive()) {
            try {
                String commandJson = JsonUtil.jsonValue(payload);
                log.info("向控制器 {} 发送命令: {}", deviceSn, commandJson);
                String mid = payload.getMid();
                CountDownLatch cdl = new CountDownLatch(1);
                LatchHolder.addLatch(mid, cdl);
                channel.writeAndFlush(commandJson + "\n");

                if (StringUtils.hasText(mid)) {
                    boolean await = cdl.await(3, TimeUnit.SECONDS);
                    if (!await) {
                        log.warn("命令 {} 超时", mid);
                        return null;
                    }

                    JavaType javaType = JsonUtil.constructType(Payload.class, clazz);
                    String resp = LatchHolder.getResp(mid);

                    if (StringUtils.hasText(resp)) {
                        Payload<T> result = JsonUtil.parseJson(resp, javaType);

                        // 如果配置了通讯完成后断开连接，则关闭通道
                        if (properties.isDisconnectAfterCommunication()) {
                            log.info("通讯完成，断开控制器 {} 的连接", deviceSn);
                            closeConnection(deviceSn);
                        }

                        return result;
                    }
                    return null;
                }
            } catch (Exception e) {
                log.error("发送命令出错", e);
            }
        } else {
            connect(deviceSn);
            log.warn("控制器 {} 的通道未激活，无法发送命令", deviceSn);
        }
        return null;
    }

    /**
     * 关闭指定控制器的连接
     *
     * @param deviceSn 控制器 sn
     */
    public void closeConnection(String deviceSn) {
        Channel channel = channelMap.get(deviceSn);
        if (channel != null && channel.isOpen()) {
            channel.close();
            channelMap.remove(deviceSn);
            setConnectState(deviceSn, false);
            log.info("控制器 {} 的连接已关闭", deviceSn);
        }
    }

    /**
     * 关闭所有连接
     */
    @PreDestroy
    public void destroy() {
        for (Map.Entry<String, Channel> entry : channelMap.entrySet()) {
            Channel channel = entry.getValue();
            if (channel != null && channel.isOpen()) {
                channel.close();
            }
        }
        channelMap.clear();
        connectionStateMap.clear();
        group.shutdownGracefully();
    }

    /**
     * 获取指定控制器的连接状态
     *
     * @param deviceSn 控制器 sn
     * @return 连接状态
     */
    public boolean getConnectState(String deviceSn) {
        AtomicBoolean state = connectionStateMap.get(deviceSn);
        return state != null && state.get();
    }

    /**
     * 设置指定控制器的连接状态
     *
     * @param deviceSn 控制器 sn
     * @param state    连接状态
     */
    public void setConnectState(String deviceSn, boolean state) {
        connectionStateMap.computeIfAbsent(deviceSn, k -> new AtomicBoolean(false)).set(state);
    }

    /**
     * 兼容旧版本的方法
     */
    public boolean getConnectState() {
        return getConnectState(properties.getDefaultDeviceSn());
    }

    /**
     * 兼容旧版本的方法
     */
    public void setConnectState(boolean state) {
        setConnectState(properties.getDefaultDeviceSn(), state);
    }

    /**
     * 兼容旧版本的方法
     */
    public <T> Payload<T> sendAndGetResp(Payload<?> payload, Class<T> clazz) {
        return sendAndGetResp(properties.getDefaultDeviceSn(), payload, clazz);
    }
}